package com.jintian.smart.kernel.core.domain;

import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.util.TypeUtils;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.beetl.sql.clazz.kit.BeetlSQLException;
import org.beetl.sql.core.LazyEntity;
import org.beetl.sql.core.Tail;
import org.springframework.beans.BeanUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public interface ITailEntity extends Tail {
    final static class Inner {
        @SuppressWarnings("rawtypes")
        private final static Cache<Class, Map<String, PropertyDescriptor>> CACHE =
                Caffeine.newBuilder().weakKeys().maximumSize(2000).build();

        private static Map<String, PropertyDescriptor> getNoExtAttrCache(Class<?> clazz) {
            Map<String, PropertyDescriptor> cache = Inner.CACHE.getIfPresent(clazz);
            if (cache == null) {
                cache = new HashMap<>();
                PropertyDescriptor[] props = BeanUtils.getPropertyDescriptors(clazz);
                for (PropertyDescriptor p : props) {
                    if (p.getWriteMethod() != null) {
                        cache.put(p.getName().toLowerCase(), p);
                    }
                }
                Inner.CACHE.put(clazz, cache);
            }
            return cache;
        }
    }

    Map<String, Object> extAttrs();

    @Override
    default Object get(String key) {
        return this.get(key, Object.class);
    }

    default <T> T get(String key, Class<T> clazz) {
        if (this.lowerExtName()) {
            key = key.toLowerCase();
        } else if (this.upperExtName()) {
            key = key.toUpperCase();
        }
        Object o = extAttrs().get(key);
        if (o instanceof LazyEntity) {
            LazyEntity lazyEntity = (LazyEntity) o;
            try {
                Object real = lazyEntity.get();
                if (real != null && !clazz.isAssignableFrom(real.getClass())) {
                    real = TypeUtils.cast(real, clazz, ParserConfig.getGlobalInstance());
                }
                real = this.set(key, real);
                return clazz.cast(real);
            } catch (RuntimeException ex) {
                throw new BeetlSQLException(BeetlSQLException.ORM_LAZY_ERROR, "Lazy Load Error:" + key + "," + ex.getMessage(), ex);
            }
        } else {
            return clazz.cast(o);
        }
    }

    @Override
    default Object set(String key, Object value) {
        Map<String, PropertyDescriptor> cache = Inner.getNoExtAttrCache(this.getClass());
        PropertyDescriptor prop = cache.get(key.toLowerCase());
        if (prop != null) {
            try {
                value = TypeUtils.cast(value, prop.getPropertyType(), ParserConfig.getGlobalInstance());
                prop.getWriteMethod().invoke(this, value);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                if (e instanceof RuntimeException) {
                    throw (RuntimeException) e;
                } else {
                    throw new RuntimeException(e);
                }
            }
        } else {
            Class<?> valueClazz = this.getExtAttrClass(key);
            if (valueClazz != null && valueClazz != Object.class) {
                value = TypeUtils.cast(value, this.getExtAttrClass(key), ParserConfig.getGlobalInstance());
            }
            if (this.lowerExtName()) {
                key = key.toLowerCase();
            } else if (this.upperExtName()) {
                key = key.toUpperCase();
            }
            this.extAttrs().put(key, value);
        }
        return value;
    }

    /**
     * 可指定扩展字段的数据类型，Controller进行序列化和反序列化时可精确转换
     *
     * @param name
     * @return
     */
    default Class<?> getExtAttrClass(String name) {
        return Object.class;
    }
}
